#include <sys/types.h>
#include <sys/socket.h>
#include <sys/uio.h>
#include <stdio.h>
#include <netinet/in.h>
#include <sys/time.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>
#include <fcntl.h>

#include "libmod.h"
#include "log.h"
#include "confparser.h"
#include "http.h"
#include "utils.h"

int mod_init(char *);

static ssize_t	writen(int fd, const void *vptr, size_t n);
static void split_url_para(char *uri, char **url, char **para);  
static FILE *check_file(char *url, char *file_path_buf);                                     
const char *getmimetype(const char *name);
static void _send_file(FILE *handler, HTTP_REQUEST *req_info, ssize_t content_length);
static int create_response_header(char *buf, const char *status, char *host, 
                                   char *url, ssize_t file_size, const char *file_mime);

const char *default_page[] = {SITE_DEFAULT_PAGE, NULL};
char SITE_ROOT_DIR[SITE_ROOT_DIR_LENGTH + 1]; 

const char http_404[] = "<html><head><title>404 Not Found</title></head><body>\
<h1>404 Not Found</h1><h2>The document requested was not found.</h2></body></html>";
const char http_500[] = "<html><head><title>500 Server Error</title></head><body>\
<h1>500 Server Error</h1><h2>Server Internal Error.</h2></body></html>";
const char http_414[] = "<html><head><title>414 Bad Request</title></head><body>\
<h1>414 Bad Request</h1><h2>Request-URI Too Long.</h2></body></html>";


static struct conf_str_config conf_str_array[] = {
	{"site_root_dir", SITE_ROOT_DIR}, 
	{0, 0}
};


/* the mime table */
struct mime_struct
{
    const char *ext;
    const char *type;
};

struct mime_struct mime_table[] = {
    { ".html", "text/html" },
    { ".htm", "text/html" },
    { ".txt", "text/plain" },
    { ".rtx", "text/richtext" },
    { ".etx", "text/x-setext" },
    { ".tsv", "text/tab-separated-values" },
    { ".css", "text/css" },
    { ".xml", "text/xml" },
    { ".dtd", "text/xml" },
    { ".gif", "image/gif" },
    { ".jpg", "image/jpeg" },
    { ".jpeg", "image/jpeg" },
    { ".jpe", "image/jpeg" },
    { ".jfif", "image/jpeg" },
    { ".tif", "image/tiff" },
    { ".tiff", "image/tiff" },
    { ".pbm", "image/x-portable-bitmap" },
    { ".pgm", "image/x-portable-graymap" },
    { ".ppm", "image/x-portable-pixmap" },
    { ".pnm", "image/x-portable-anymap" },
    { ".xbm", "image/x-xbitmap" },
    { ".xpm", "image/x-xpixmap" },
    { ".xwd", "image/x-xwindowdump" },
    { ".ief", "image/ief" },
    { ".png", "image/png" },
    { ".au", "audio/basic" },
    { ".snd", "audio/basic" },
    { ".aif", "audio/x-aiff" },
    { ".aiff", "audio/x-aiff" },
    { ".aifc", "audio/x-aiff" },
    { ".ra", "audio/x-pn-realaudio" },
    { ".ram", "audio/x-pn-realaudio" },
    { ".rm", "audio/x-pn-realaudio" },
    { ".rpm", "audio/x-pn-realaudio-plugin" },
    { ".wav", "audio/wav" },
    { ".mid", "audio/midi" },
    { ".midi", "audio/midi" },
    { ".kar", "audio/midi" },
    { ".mpga", "audio/mpeg" },
    { ".mp2", "audio/mpeg" },
    { ".mp3", "audio/mpeg" },
    { ".mpeg", "video/mpeg" },
    { ".mpg", "video/mpeg" },
    { ".mpe", "video/mpeg" },
    { ".qt", "video/quicktime" },
    { ".mov", "video/quicktime" },
    { ".avi", "video/x-msvideo" },
    { ".movie", "video/x-sgi-movie" },
    { ".mv", "video/x-sgi-movie" },
    { ".vx", "video/x-rad-screenplay" },
    { ".a", "application/octet-stream" },
    { ".bin", "application/octet-stream" },
    { ".exe", "application/octet-stream" },
    { ".dump", "application/octet-stream" },
    { ".o", "application/octet-stream" },
    { ".class", "application/java" },
    { ".js", "application/x-javascript" },
    { ".ai", "application/postscript" },
    { ".eps", "application/postscript" },
    { ".ps", "application/postscript" },
    { ".dir", "application/x-director" },
    { ".dcr", "application/x-director" },
    { ".dxr", "application/x-director" },
    { ".fgd", "application/x-director" },
    { ".aam", "application/x-authorware-map" },
    { ".aas", "application/x-authorware-seg" },
    { ".aab", "application/x-authorware-bin" },
    { ".fh4", "image/x-freehand" },
    { ".fh7", "image/x-freehand" },
    { ".fh5", "image/x-freehand" },
    { ".fhc", "image/x-freehand" },
    { ".fh", "image/x-freehand" },
    { ".spl", "application/futuresplash" },
    { ".swf", "application/x-shockwave-flash" },
    { ".dvi", "application/x-dvi" },
    { ".gtar", "application/x-gtar" },
    { ".hdf", "application/x-hdf" },
    { ".hqx", "application/mac-binhex40" },
    { ".iv", "application/x-inventor" },
    { ".latex", "application/x-latex" },
    { ".man", "application/x-troff-man" },
    { ".me", "application/x-troff-me" },
    { ".mif", "application/x-mif" },
    { ".ms", "application/x-troff-ms" },
    { ".oda", "application/oda" },
    { ".pdf", "application/pdf" },
    { ".rtf", "application/rtf" },
    { ".bcpio", "application/x-bcpio" },
    { ".cpio", "application/x-cpio" },
    { ".sv4cpio", "application/x-sv4cpio" },
    { ".sv4crc", "application/x-sv4crc" },
    { ".sh", "application/x-shar" },
    { ".shar", "application/x-shar" },
    { ".sit", "application/x-stuffit" },
    { ".tar", "application/x-tar" },
    { ".tex", "application/x-tex" },
    { ".texi", "application/x-texinfo" },
    { ".texinfo", "application/x-texinfo" },
    { ".tr", "application/x-troff" },
    { ".roff", "application/x-troff" },
    { ".man", "application/x-troff-man" },
    { ".me", "application/x-troff-me" },
    { ".ms", "application/x-troff-ms" },
    { ".zip", "application/x-zip-compressed" },
    { ".tsp", "application/dsptype" },
    { ".wsrc", "application/x-wais-source" },
    { ".ustar", "application/x-ustar" },
    { ".cdf", "application/x-netcdf" },
    { ".nc", "application/x-netcdf" },
    { ".doc", "application/msword" },
    { ".ppt", "application/powerpoint" },
    { ".wrl", "model/vrml" },
    { ".vrml", "model/vrml" },
    { ".mime", "message/rfc822" },
    { ".pac", "application/x-ns-proxy-autoconfig" },
    { ".wml", "text/vnd.wap.wml" },
    { ".wmlc", "application/vnd.wap.wmlc" },
    { ".wmls", "text/vnd.wap.wmlscript" },
    { ".wmlsc", "application/vnd.wap.wmlscriptc" },
    { ".wbmp", "image/vnd.wap.wbmp" },
    { ".tgz", "application/x-gzip" },
    { ".tar.gz", "application/x-gzip" },
    { ".bz2", "application/x-bzip2" },
    { ".rar", "application/x-zip-compressed" },
    { ".iso", "application/x-zip-compressed" }
};


/* get the mime type via extand name */
const char *
getmimetype(const char *name) 
{
    unsigned int namelen, extlen, i;
    
    if (name == NULL)
    {
        return mime_table[0].type;
    }
    
    namelen = strlen(name);
    
    for (i=0; i < sizeof(mime_table)/sizeof(*mime_table); i++) 
    {
        extlen = strlen(mime_table[i].ext);
        if (extlen != namelen)
        {
            continue;
        }

        if (strcmp(name, mime_table[i].ext) == 0)
        {
            return mime_table[i].type;
        }
    }
    return mime_table[0].type;
}


/* write n bytes to a fd */
static ssize_t	
writen(int fd, const void *vptr, size_t n)
{
	size_t		nleft;
	ssize_t		nwritten;
	const char	*ptr;

	ptr = vptr;
	nleft = n;
	while (nleft > 0) {
		if ( (nwritten = write(fd, ptr, nleft)) <= 0) {
			if (errno == EINTR)
				nwritten = 0;		/* and call write() again */
			else
				return(-1);			/* error */
		}

		nleft -= nwritten;
		ptr   += nwritten;
	}
	return(n);
}


/* split url and parameters 
 * in:  uri : /aaa.htm?b=1  
 * out: url : /aaa.htm  para : b=1
 */
static void
split_url_para(char *uri, char **url, char **para)
{
    if (uri == NULL)
    {
        return;
    }
    
    /* parse the uri. split from '?' */
    *url = uri;
    while ( *uri != '\0')
    {
        if ( *uri == '?')
        {
            *uri = '\0';
            if (*(uri + 1) != '\0')
            {
                *para = uri + 1;
            }
            return;
        }
        uri++;
    }
}


/* 
 * check a file by given url.
 * if the file exists and can be opened, a file pointer FILE * is returned,
 * and the file_path_buf is set to the actually opened file path.
 * otherwise, a NULL pointer is returned and file_path_buf is ignored.
 */
static FILE *
check_file(char *url, char *file_path_buf)
{
    FILE *handler;
    char file_path[REQUEST_URL_MAX_LENGTH + sizeof(SITE_ROOT_DIR)];
    char file_path_temp[REQUEST_URL_MAX_LENGTH + sizeof(SITE_ROOT_DIR)];
    const char **default_page_t;
    
    if (url == NULL || file_path_buf == NULL)
    {
        return NULL;
    }
    
    /* join the site_root and url together */
    strlcpy(file_path, SITE_ROOT_DIR, sizeof(file_path));
    strlcat(file_path, url, sizeof(file_path));
    
    #ifdef DEBUG
    printf("file_path:%s\n",file_path);
    #endif
    
    /* check this is whether a directory path or a file path */
    if (file_path[strlen(file_path) - 1] == '/')    /* this is a directory path */
    {
        #ifdef DEBUG
        printf("dir path\n");
        #endif
        /* open the default page if exist.otherwise return 404 page not found */
        default_page_t = default_page;
        while (*default_page_t != NULL)
        {
            strlcpy(file_path_temp, file_path, sizeof(file_path_temp));
            strlcat(file_path_temp, *default_page_t, sizeof(file_path_temp));
            
            #ifdef DEBUG
            printf("final path:%s\n",file_path_temp);
            #endif
           
            default_page_t++;
            
            handler = fopen(file_path_temp, "rb");
            if (handler != NULL)
            {
                strcpy(file_path_buf, file_path_temp);
                return handler;
            }
        }
        
        if (handler == NULL)
        {
            return NULL;
        }
    }
    else    /* this is a file path */
    {   
        #ifdef DEBUG
        printf("file path\n");
        #endif
        
        strcpy(file_path_buf, file_path);
        handler = fopen(file_path, "rb");
        return handler;
    }
    
    return NULL;
}


/* send the given file via socket */
static void
_send_file(FILE *handler, HTTP_REQUEST *req_info, ssize_t content_length)
{
    int64_t flags;

    /* Set socket to blocking */ 
    if ((flags = fcntl(req_info->request_fd, F_GETFL, 0)) < 0) 
    { 
        WLOG_ERR("[HTTP] error when get the socket flag"); 
        return;
    } 

    if (fcntl(req_info->request_fd, F_SETFL, flags & (~O_NONBLOCK)) < 0) 
    { 
        WLOG_ERR("[HTTP] error when set socket to blocking mod"); 
        return;
    } 
    
    /* sending file with read & write system call */
    #if HOW_TO_RESPONSE_FILE == COMMON
    int64_t read_objects = 0;
    int64_t write_objects = 0;
    unsigned char buf[RESPONSE_FILE_BUF_LENGTH];
    
    CSF_UNUSED_ARG(content_length);
    while (feof(handler) == 0)
    {
        read_objects = fread(buf, 1, RESPONSE_FILE_BUF_LENGTH, handler);
        if (read_objects < 0)
        {
            WLOG_ERR("[HTTP] error when response a file"); 
            break;
        }
        write_objects = write(req_info->request_fd, buf, read_objects);
        if (write_objects < 0)
        {
            WLOG_ERR("[HTTP] error when response a file"); 
            //perror("");
            break;
        }

        #ifdef DEBUG
        printf("read = %ld\n", read_objects);
        printf("write = %ld\n", write_objects);
        #endif
    }
    #endif
    
    /* sending file with sendfile system call */
    #if HOW_TO_RESPONSE_FILE == FREEBSD
    CSF_UNUSED_ARG(content_length);
    flags = sendfile(fileno(handler), req_info->request_fd, 0, 0, NULL, NULL, 0);
    #endif
    
    #if HOW_TO_RESPONSE_FILE == LINUX
    flags = sendfile(req_info->request_fd, fileno(handler), (off_t *)0 , (size_t)content_length);
    #endif
    
    if (flags == -1)
    {
        WLOG_ERR("[http]response file failed. %s", strerror(errno));
    }
    
    /* Set socket back to non-blocking */ 
    if ((flags = fcntl(req_info->request_fd, F_GETFL, 0)) < 0) 
    { 
        WLOG_ERR("[HTTP] error when get the socket flag"); 
        return;
    } 

    if (fcntl(req_info->request_fd, F_SETFL, flags | O_NONBLOCK) < 0)
    { 
        WLOG_ERR("[HTTP] error when set socket to non-blocking mod"); 
        return;
    }
    
}


/* create the response header, result in char *buf */
static int 
create_response_header(char *buf, const char *status, char *host, char *url, 
                       ssize_t file_size, const char *file_mime)
{
    struct tm *tm_ptr;
    time_t the_time;
    char *asc_time;
    const char *server;
    
    if (buf == NULL || status == NULL || host == NULL || url == NULL
        || file_mime == NULL)
    {
        return -1;
    }
    
    /* get host time */
    time(&the_time);
    tm_ptr = gmtime(&the_time);
    asc_time = asctime(tm_ptr);
    asc_time[strlen(asc_time) - 1] = '\0';  /* delete the last '\n' */
    
    server = "CSF Http Server 1.0";         /* server information */
    
    snprintf(buf, RESPONSE_BUF_MAX_LENGTH, 
                "%s\r\nDate: %s\r\nServer: %s\r\nLocation: %s%s\r\n"
                "Content-Length: %ld\r\nContent-Type: %s\r\n"
                "\r\n",
                status, asc_time, server, host, url, file_size, file_mime);
   
    return 1;
}


static ssize_t 
socket_handler(WTI *wtip, CONN_INFO *cip, void *data, void **res)
{
    FILE *handler = NULL;
    HTTP_REQUEST *req_info;   
    char *url = NULL;
    char is_file_opened = 0;
    char *parameter = NULL;
    char *extand_name = NULL;
    char response_buf[RESPONSE_BUF_MAX_LENGTH];
    char file_path_buf[REQUEST_URL_MAX_LENGTH];
    const char status[] = "HTTP/1.0 200 OK";
    const char *content_type = NULL;
    const char *response_entity = NULL;
    ssize_t content_length;
    struct stat st;
    
    CSF_UNUSED_ARG(res);
    CSF_UNUSED_ARG(cip);
    CSF_UNUSED_ARG(wtip);
    
    req_info = (HTTP_REQUEST *)data;
    memset(response_buf, '\0', RESPONSE_BUF_MAX_LENGTH);
    memset(file_path_buf, '\0', REQUEST_URL_MAX_LENGTH);

    /* split url and parameters index[1] is 'uri' */
    split_url_para(req_info->header_field_ptr[URI].field_value, &url, &parameter);

    if (url == NULL)
    {
        WLOG_ERR("[HTTP] parsing url failed. bad request header.");
        return (PIPELINE_FAILED);
    }

    /* http 414  request uri too long */
    if (strlen(url) > REQUEST_URL_MAX_LENGTH)
    {
        content_type = getmimetype("text/html");
        content_length = sizeof(http_414) - 1;
        response_entity = http_414;
    }
    
    #ifdef DEBUG
    printf("url:%s\n",url);
    printf("parameter:%s\n",parameter);
    #endif
       
    /* check the requset resource and set the actual file path*/
    handler = check_file(url, file_path_buf);
    if (handler == NULL)        /* http 404 */
    {
        content_type = getmimetype("text/html");
        content_length = sizeof(http_404) - 1;
        response_entity = http_404;
    }
    else                        /* file is available */
    {
        is_file_opened = 1;
        
        if (fstat(fileno(handler), &st) < 0)    /* can not get file size http 500 */
        {
            content_type = getmimetype("text/html");
            content_length = sizeof(http_500) - 1;
            response_entity = http_500;
        }
        else    /* every thing goes well, we send the file via socket */
        {
            /* get the extend name of the file via actual path */
            extand_name = file_path_buf;
            while (*extand_name != '\0')
            {
                if (*extand_name == '.')break;
                extand_name++;
            }
            
            /* get the mime type via extand name */
            content_type = getmimetype(extand_name);
            content_length = st.st_size;
            response_entity = NULL;
        }
    }
    
    /* send http head */
    create_response_header(response_buf, status, 
                               req_info->header_field_ptr[HOST].field_value, url, 
                               content_length, content_type);

    
    /* send header */                          
    writen(req_info->request_fd, response_buf, strlen(response_buf));
    
    /* send entity */
    if (response_entity == NULL)
    {
        _send_file(handler, req_info, content_length);
    }
    else
    {
        writen(req_info->request_fd, response_entity, content_length);
    }
    
    if (is_file_opened)
    {
        fclose(handler);
    }
    
    req_info->state = HTTP_PIPE_LINE_DONE;
    
    #ifdef DEBUG
    printf("\n***final path:***\n%s\n", file_path_buf);
    printf("\n***final response:***\n%s\n", response_buf);
    #endif
    
    return (PIPELINE_CONTINUE);
}


static ssize_t
http_init(void *arg, void **res)
{
	char mod_name[] = "http_io.so";
	int r;

	CSF_UNUSED_ARG(res);
	
	if(((WTI *)arg)->thr_seq_num == 0)
	{ 
		r = load_conf(NULL, mod_name, NULL, conf_str_array);
	
		if (r != 0)
		{
			WLOG_ERR("http_init load_conf() failed!");	
		}
	}
		
    return MOD_INIT_OK;
}

static void
http_deinit(void *arg, void *res)
{
	CSF_UNUSED_ARG(arg);
	CSF_UNUSED_ARG(res);
	
	if(((WTI *)arg)->thr_seq_num == 0)
	{
		printf("http deinit.\n");
	}
		
    return;
}


int 
mod_init(char *mod_name)
{
	CSF_UNUSED_ARG(mod_name);
	
	set_request_init(http_init);
	set_request_deinit(http_deinit);
	set_request_handler(socket_handler);

	return (0);
}





